/*
 * This file is part of WebLookAndFeel library.
 *
 * WebLookAndFeel library is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * WebLookAndFeel library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with WebLookAndFeel library.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.alee.laf;

import com.alee.extended.label.WebMultiLineLabelUI;
import com.alee.extended.label.WebVerticalLabelUI;
import com.alee.laf.button.WebButtonUI;
import com.alee.laf.button.WebToggleButtonUI;
import com.alee.laf.checkbox.WebCheckBoxUI;
import com.alee.laf.colorchooser.WebColorChooserUI;
import com.alee.laf.combobox.WebComboBoxUI;
import com.alee.laf.desktoppane.WebDesktopIconUI;
import com.alee.laf.desktoppane.WebDesktopPaneUI;
import com.alee.laf.desktoppane.WebInternalFrameUI;
import com.alee.laf.filechooser.WebFileChooserUI;
import com.alee.laf.label.WebLabelUI;
import com.alee.laf.list.WebListCellRenderer;
import com.alee.laf.list.WebListStyle;
import com.alee.laf.list.WebListUI;
import com.alee.laf.menu.*;
import com.alee.laf.optionpane.WebOptionPaneUI;
import com.alee.laf.panel.WebPanelUI;
import com.alee.laf.progressbar.WebProgressBarUI;
import com.alee.laf.radiobutton.WebRadioButtonUI;
import com.alee.laf.rootpane.WebRootPaneUI;
import com.alee.laf.scroll.WebScrollBarStyle;
import com.alee.laf.scroll.WebScrollBarUI;
import com.alee.laf.scroll.WebScrollPaneUI;
import com.alee.laf.separator.WebSeparatorUI;
import com.alee.laf.slider.WebSliderUI;
import com.alee.laf.spinner.WebSpinnerUI;
import com.alee.laf.splitpane.WebSplitPaneUI;
import com.alee.laf.tabbedpane.WebTabbedPaneUI;
import com.alee.laf.table.WebTableHeaderUI;
import com.alee.laf.table.WebTableStyle;
import com.alee.laf.table.WebTableUI;
import com.alee.laf.text.*;
import com.alee.laf.toolbar.WebToolBarSeparatorUI;
import com.alee.laf.toolbar.WebToolBarUI;
import com.alee.laf.tooltip.WebToolTipUI;
import com.alee.laf.tree.WebTreeUI;
import com.alee.laf.viewport.WebViewportUI;
import com.alee.managers.focus.FocusManager;
import com.alee.managers.hotkey.HotkeyManager;
import com.alee.managers.language.LanguageManager;
import com.alee.managers.proxy.ProxyManager;
import com.alee.managers.settings.SettingsManager;
import com.alee.managers.tooltip.TooltipManager;
import com.alee.managers.version.VersionManager;
import com.alee.utils.*;
import com.alee.utils.swing.SwingLazyValue;

import javax.swing.*;
import javax.swing.plaf.InsetsUIResource;
import javax.swing.plaf.basic.BasicLookAndFeel;
import javax.swing.text.DefaultEditorKit;
import java.awt.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.List;

/**
 * This core class contains methods to install, configure and uninstall WebLookAndFeel.
 *
 * @author Mikle Garin
 * @since 1.0
 */

public class WebLookAndFeel extends BasicLookAndFeel
{
    /**
     * Some known UI constants.
     */
    public static final String COMPONENT_ORIENTATION_PROPERTY = "componentOrientation";
    public static final String COMPONENT_MARGIN_PROPERTY = "margin";
    public static final String COMPONENT_ENABLED_PROPERTY = "enabled";
    public static final String TOOLBAR_FLOATABLE_PROPERTY = "floatable";
    public static final String WINDOW_DECORATION_STYLE_PROPERTY = "windowDecorationStyle";

    /**
     * List of WebLookAndFeel icons.
     */
    private static List<ImageIcon> icons = null;

    /**
     * Alt hotkey processor for application windows with menu.
     */
    public static final AltProcessor altProcessor = new AltProcessor ();

    /**
     * Whether to hide component mnemonics by default or not.
     */
    private static boolean isMnemonicHidden = true;

    /**
     * Whether all frames should be decorated using WebLaF styling by default or not.
     */
    private static boolean decorateFrames = false;

    /**
     * Whether all dialogs should be decorated using WebLaF styling by default or not.
     */
    private static boolean decorateDialogs = false;

    /**
     * Reassignable LookAndFeel UI class names.
     */

    // Label
    public static String labelUI = WebLabelUI.class.getCanonicalName ();
    public static String toolTipUI = WebToolTipUI.class.getCanonicalName ();

    // Button
    public static String buttonUI = WebButtonUI.class.getCanonicalName ();
    public static String toggleButtonUI = WebToggleButtonUI.class.getCanonicalName ();
    public static String checkBoxUI = WebCheckBoxUI.class.getCanonicalName ();
    public static String radioButtonUI = WebRadioButtonUI.class.getCanonicalName ();

    // Menu
    public static String menuBarUI = WebMenuBarUI.class.getCanonicalName ();
    public static String menuUI = WebMenuUI.class.getCanonicalName ();
    public static String popupMenuUI = WebPopupMenuUI.class.getCanonicalName ();
    public static String menuItemUI = WebMenuItemUI.class.getCanonicalName ();
    public static String checkBoxMenuItemUI = WebCheckBoxMenuItemUI.class.getCanonicalName ();
    public static String radioButtonMenuItemUI = WebRadioButtonMenuItemUI.class.getCanonicalName ();
    public static String popupMenuSeparatorUI = WebPopupMenuSeparatorUI.class.getCanonicalName ();

    // Separator
    public static String separatorUI = WebSeparatorUI.class.getCanonicalName ();

    // Scroll
    public static String scrollBarUI = WebScrollBarUI.class.getCanonicalName ();
    public static String scrollPaneUI = WebScrollPaneUI.class.getCanonicalName ();

    // Text
    public static String textFieldUI = WebTextFieldUI.class.getCanonicalName ();
    public static String passwordFieldUI = WebPasswordFieldUI.class.getCanonicalName ();
    public static String formattedTextFieldUI = WebFormattedTextFieldUI.class.getCanonicalName ();
    public static String textAreaUI = WebTextAreaUI.class.getCanonicalName ();
    public static String editorPaneUI = WebEditorPaneUI.class.getCanonicalName ();
    public static String textPaneUI = WebTextPaneUI.class.getCanonicalName ();

    // Toolbar
    public static String toolBarUI = WebToolBarUI.class.getCanonicalName ();
    public static String toolBarSeparatorUI = WebToolBarSeparatorUI.class.getCanonicalName ();

    // Table
    public static String tableUI = WebTableUI.class.getCanonicalName ();
    public static String tableHeaderUI = WebTableHeaderUI.class.getCanonicalName ();

    // Chooser
    public static String colorChooserUI = WebColorChooserUI.class.getCanonicalName ();
    public static String fileChooserUI = WebFileChooserUI.class.getCanonicalName ();

    // Container
    public static String panelUI = WebPanelUI.class.getCanonicalName ();
    public static String viewportUI = WebViewportUI.class.getCanonicalName ();
    public static String rootPaneUI = WebRootPaneUI.class.getCanonicalName ();
    public static String tabbedPaneUI = WebTabbedPaneUI.class.getCanonicalName ();
    public static String splitPaneUI = WebSplitPaneUI.class.getCanonicalName ();

    // Complex components
    public static String progressBarUI = WebProgressBarUI.class.getCanonicalName ();
    public static String sliderUI = WebSliderUI.class.getCanonicalName ();
    public static String spinnerUI = WebSpinnerUI.class.getCanonicalName ();
    public static String treeUI = WebTreeUI.class.getCanonicalName ();
    public static String listUI = WebListUI.class.getCanonicalName ();
    public static String comboBoxUI = WebComboBoxUI.class.getCanonicalName ();

    // Desktop pane
    public static String desktopPaneUI = WebDesktopPaneUI.class.getCanonicalName ();
    public static String descktopIconUI = WebDesktopIconUI.class.getCanonicalName ();
    public static String internalFrameUI = WebInternalFrameUI.class.getCanonicalName ();

    // Option pane
    public static String optionPaneUI = WebOptionPaneUI.class.getCanonicalName ();

    /**
     * WebLaF specific components reassignable UI class names.
     */

    // Label
    public static String verticalLabelUI = WebVerticalLabelUI.class.getCanonicalName ();
    public static String multiLineLabelUI = WebMultiLineLabelUI.class.getCanonicalName ();

    /**
     * Reassignable LookAndFeel fonts.
     */

    // Text components fonts
    public static Font globalTextFont = new Font ( Font.DIALOG, Font.PLAIN, 12 );
    public static Font buttonFont;
    public static Font toggleButtonFont;
    public static Font radioButtonFont;
    public static Font checkBoxFont;
    public static Font colorChooserFont;
    public static Font labelFont;
    public static Font listFont;
    public static Font menuBarFont;
    public static Font menuItemFont;
    public static Font radioButtonMenuItemFont;
    public static Font checkBoxMenuItemFont;
    public static Font menuFont;
    public static Font popupMenuFont;
    public static Font optionPaneFont;
    public static Font panelFont;
    public static Font progressBarFont;
    public static Font scrollPaneFont;
    public static Font viewportFont;
    public static Font sliderFont;
    public static Font tabbedPaneFont;
    public static Font tableFont;
    public static Font tableHeaderFont;
    public static Font titledBorderFont;
    public static Font toolBarFont;
    public static Font toolTipFont;
    public static Font treeFont;

    // Component's accelerators fonts
    public static Font globalAcceleratorFont = new Font ( Font.DIALOG, Font.PLAIN, 11 );
    public static Font menuItemAcceleratorFont;
    public static Font radioButtonMenuItemAcceleratorFont;
    public static Font checkBoxMenuItemAcceleratorFont;
    public static Font menuAcceleratorFont;

    // Title components fonts
    public static Font globalTitleFont = new Font ( Font.DIALOG, Font.BOLD, 12 );
    public static Font internalFrameFont;

    // Editor components fonts
    public static Font globalEditorsFont = new Font ( Font.DIALOG, Font.PLAIN, 12 );
    public static Font comboBoxFont;
    public static Font spinnerFont;
    public static Font textFieldFont;
    public static Font formattedTextFieldFont;
    public static Font passwordFieldFont;
    public static Font textAreaFont;
    public static Font textPaneFont;
    public static Font editorPaneFont;

    /**
     * Returns WebLookAndFeel name.
     *
     * @return WebLookAndFeel name
     */
    public String getName ()
    {
        return "WebLookAndFeel";
    }

    /**
     * Returns unique WebLookAndFeel ID.
     *
     * @return unique WebLookAndFeel ID
     */
    public String getID ()
    {
        return "WebLookAndFeel";
    }

    /**
     * Returns short WebLookAndFeel description.
     *
     * @return short WebLookAndFeel description
     */
    public String getDescription ()
    {
        return "Cross-platform stylish Look and Feel";
    }

    /**
     * Always returns false since WebLookAndFeel is not native for any platform.
     *
     * @return false
     */
    public boolean isNativeLookAndFeel ()
    {
        return false;
    }

    /**
     * Always returns true since WebLookAndFeel supports any platform which can run Java applications.
     *
     * @return true
     */
    public boolean isSupportedLookAndFeel ()
    {
        return true;
    }

    /**
     * Returns whether window decorations are supported for undelying system.
     *
     * @return true if window decorations are supported for undelying system, false otherwise
     */
    public boolean getSupportsWindowDecorations ()
    {
        // Only win and mac systems properly support window transparency states
        return SystemUtils.isWindows () || SystemUtils.isMac ();
    }

    /**
     * Initializes WebLookAndFeel UI classes.
     *
     * @param table UIDefaults table
     */
    protected void initClassDefaults ( UIDefaults table )
    {
        // Label
        table.put ( "LabelUI", labelUI );
        table.put ( "ToolTipUI", toolTipUI );

        // Button
        table.put ( "ButtonUI", buttonUI );
        table.put ( "ToggleButtonUI", toggleButtonUI );
        table.put ( "CheckBoxUI", checkBoxUI );
        table.put ( "RadioButtonUI", radioButtonUI );

        // Menu
        table.put ( "MenuBarUI", menuBarUI );
        table.put ( "MenuUI", menuUI );
        table.put ( "PopupMenuUI", popupMenuUI );
        table.put ( "MenuItemUI", menuItemUI );
        table.put ( "CheckBoxMenuItemUI", checkBoxMenuItemUI );
        table.put ( "RadioButtonMenuItemUI", radioButtonMenuItemUI );
        table.put ( "PopupMenuSeparatorUI", popupMenuSeparatorUI );

        // Separator
        table.put ( "SeparatorUI", separatorUI );

        // Scroll
        table.put ( "ScrollBarUI", scrollBarUI );
        table.put ( "ScrollPaneUI", scrollPaneUI );

        // Text
        table.put ( "TextFieldUI", textFieldUI );
        table.put ( "PasswordFieldUI", passwordFieldUI );
        table.put ( "FormattedTextFieldUI", formattedTextFieldUI );
        table.put ( "TextAreaUI", textAreaUI );
        table.put ( "EditorPaneUI", editorPaneUI );
        table.put ( "TextPaneUI", textPaneUI );

        // Toolbar
        table.put ( "ToolBarUI", toolBarUI );
        table.put ( "ToolBarSeparatorUI", toolBarSeparatorUI );

        // Table
        table.put ( "TableUI", tableUI );
        table.put ( "TableHeaderUI", tableHeaderUI );

        // Chooser
        table.put ( "ColorChooserUI", colorChooserUI );
        table.put ( "FileChooserUI", fileChooserUI );

        // Container
        table.put ( "PanelUI", panelUI );
        table.put ( "ViewportUI", viewportUI );
        table.put ( "RootPaneUI", rootPaneUI );
        table.put ( "TabbedPaneUI", tabbedPaneUI );
        table.put ( "SplitPaneUI", splitPaneUI );

        // Complex components
        table.put ( "ProgressBarUI", progressBarUI );
        table.put ( "SliderUI", sliderUI );
        table.put ( "SpinnerUI", spinnerUI );
        table.put ( "TreeUI", treeUI );
        table.put ( "ListUI", listUI );
        table.put ( "ComboBoxUI", comboBoxUI );

        // Desktop pane
        table.put ( "DesktopPaneUI", desktopPaneUI );
        table.put ( "DesktopIconUI", descktopIconUI );
        table.put ( "InternalFrameUI", internalFrameUI );

        // Option pane
        table.put ( "OptionPaneUI", optionPaneUI );
    }

    /**
     * Initializes WebLookAndFeel defaults (like default renderers, component borders and such).
     * This method will be called only in case WebLookAndFeel is installed through UIManager as current application LookAndFeel.
     *
     * @param table
     */
    protected void initComponentDefaults ( UIDefaults table )
    {
        super.initComponentDefaults ( table );

        // Global text antialiasing
        // table.put ( SwingUtilities2.AA_TEXT_PROPERTY_KEY, SwingUtilities2.AATextInfo.getAATextInfo ( true ) );

        // Fonts
        initializeFonts ( table );

        // Option pane
        table.put ( "OptionPane.messageAreaBorder",
                new SwingLazyValue ( "javax.swing.plaf.BorderUIResource$EmptyBorderUIResource", new Object[]{ 0, 0, 5, 0 } ) );

        // HTML image icons
        table.put ( "html.pendingImage", StyleConstants.htmlPendingIcon );
        table.put ( "html.missingImage", StyleConstants.htmlMissingIcon );

        // Scroll bars minimum size
        table.put ( "ScrollBar.minimumThumbSize", new Dimension ( WebScrollBarStyle.minThumbWidth, WebScrollBarStyle.minThumbHeight ) );

        // Tree icons
        table.put ( "Tree.openIcon", WebTreeUI.OPEN_ICON );
        table.put ( "Tree.closedIcon", WebTreeUI.CLOSED_ICON );
        table.put ( "Tree.leafIcon", WebTreeUI.LEAF_ICON );
        table.put ( "Tree.collapsedIcon", WebTreeUI.EXPAND_ICON );
        table.put ( "Tree.expandedIcon", WebTreeUI.COLLAPSE_ICON );
        // Tree default selection style
        table.put ( "Tree.textForeground", Color.BLACK );
        table.put ( "Tree.textBackground", StyleConstants.transparent );
        table.put ( "Tree.selectionForeground", Color.BLACK );
        table.put ( "Tree.selectionBackground", StyleConstants.transparent );
        table.put ( "Tree.selectionBorderColor", StyleConstants.transparent );
        // Tree default renderer content margins
        table.put ( "Tree.rendererMargins", new InsetsUIResource ( 4, 4, 4, 6 ) );
        table.put ( "Tree.rendererFillBackground", Boolean.FALSE );
        table.put ( "Tree.drawsFocusBorderAroundIcon", Boolean.FALSE );
        table.put ( "Tree.drawDashedFocusIndicator", Boolean.FALSE );

        // Disabled foreground color
        table.put ( "MenuItem.disabledForeground", StyleConstants.disabledTextColor );
        // We don't need uneven heights
        table.put ( "MenuItem.evenHeight", Boolean.TRUE );

        // JMenu expand spacing
        // Up-down menu expand
        table.put ( "Menu.menuPopupOffsetX", StyleConstants.shadeWidth );
        table.put ( "Menu.menuPopupOffsetY", -StyleConstants.shadeWidth / 2 );
        // Left-right menu expand
        table.put ( "Menu.submenuPopupOffsetX", StyleConstants.shadeWidth / 2 );
        table.put ( "Menu.submenuPopupOffsetY", 0 );

        // Table defaults
        table.put ( "Table.cellNoFocusBorder", LafUtils.createWebBorder ( 1, 1, 1, 1 ) );
        table.put ( "Table.focusSelectedCellHighlightBorder", LafUtils.createWebBorder ( 1, 1, 1, 1 ) );
        table.put ( "Table.focusCellHighlightBorder", LafUtils.createWebBorder ( 1, 1, 1, 1 ) );
        table.put ( "Table.foreground", WebTableStyle.foreground );
        table.put ( "Table.background", WebTableStyle.background );
        table.put ( "Table.selectionForeground", WebTableStyle.selectionForeground );
        table.put ( "Table.selectionBackground", WebTableStyle.selectionBackground );
        // Table header defaults
        table.put ( "TableHeader.cellBorder", LafUtils.createWebBorder ( WebTableStyle.headerMargin ) );
        table.put ( "TableHeader.focusCellBorder", LafUtils.createWebBorder ( WebTableStyle.headerMargin ) );

        // Default list renderer
        table.put ( "List.cellRenderer", new UIDefaults.ActiveValue ()
        {
            public Object createValue ( UIDefaults table )
            {
                return new WebListCellRenderer.UIResource ();
            }
        } );
        // List selection foreground
        table.put ( "List.selectionForeground", WebListStyle.foreground );

        // Combobox selection foregrounds
        table.put ( "ComboBox.selectionForeground", Color.BLACK );
        // Combobox non-square arrow
        table.put ( "ComboBox.squareButton", false );

        // Default components borders
        table.put ( "ProgressBar.border", new SwingLazyValue ( "com.alee.laf.WebBorders", "getProgressBarBorder" ) );
        table.put ( "Button.border", new SwingLazyValue ( "com.alee.laf.WebBorders", "getButtonBorder" ) );

        // WebTextField actions
        table.put ( "TextField.focusInputMap", new UIDefaults.LazyInputMap (
                new Object[]{ "control C", DefaultEditorKit.copyAction, "control V", DefaultEditorKit.pasteAction, "control X",
                        DefaultEditorKit.cutAction, "COPY", DefaultEditorKit.copyAction, "PASTE", DefaultEditorKit.pasteAction, "CUT",
                        DefaultEditorKit.cutAction, "control INSERT", DefaultEditorKit.copyAction, "shift INSERT",
                        DefaultEditorKit.pasteAction, "shift DELETE", DefaultEditorKit.cutAction, "control A",
                        DefaultEditorKit.selectAllAction, "control BACK_SLASH", "unselect"
                        /*DefaultEditorKit.unselectAction*/, "shift LEFT", DefaultEditorKit.selectionBackwardAction, "shift RIGHT",
                        DefaultEditorKit.selectionForwardAction, "control LEFT", DefaultEditorKit.previousWordAction, "control RIGHT",
                        DefaultEditorKit.nextWordAction, "control shift LEFT", DefaultEditorKit.selectionPreviousWordAction,
                        "control shift RIGHT", DefaultEditorKit.selectionNextWordAction, "HOME", DefaultEditorKit.beginLineAction, "END",
                        DefaultEditorKit.endLineAction, "shift HOME", DefaultEditorKit.selectionBeginLineAction, "shift END",
                        DefaultEditorKit.selectionEndLineAction, "BACK_SPACE", DefaultEditorKit.deletePrevCharAction, "shift BACK_SPACE",
                        DefaultEditorKit.deletePrevCharAction, "ctrl H", DefaultEditorKit.deletePrevCharAction, "DELETE",
                        DefaultEditorKit.deleteNextCharAction, "ctrl DELETE", DefaultEditorKit.deleteNextWordAction, "ctrl BACK_SPACE",
                        DefaultEditorKit.deletePrevWordAction, "RIGHT", DefaultEditorKit.forwardAction, "LEFT",
                        DefaultEditorKit.backwardAction, "KP_RIGHT", DefaultEditorKit.forwardAction, "KP_LEFT",
                        DefaultEditorKit.backwardAction, "ENTER", JTextField.notifyAction, "control shift O", "toggle-componentOrientation"
                        /*DefaultEditorKit.toggleComponentOrientation*/ } ) );

        // WebPasswordField actions
        table.put ( "PasswordField.focusInputMap", new UIDefaults.LazyInputMap (
                new Object[]{ "control C", DefaultEditorKit.copyAction, "control V", DefaultEditorKit.pasteAction, "control X",
                        DefaultEditorKit.cutAction, "COPY", DefaultEditorKit.copyAction, "PASTE", DefaultEditorKit.pasteAction, "CUT",
                        DefaultEditorKit.cutAction, "control INSERT", DefaultEditorKit.copyAction, "shift INSERT",
                        DefaultEditorKit.pasteAction, "shift DELETE", DefaultEditorKit.cutAction, "control A",
                        DefaultEditorKit.selectAllAction, "control BACK_SLASH", "unselect"
                        /*DefaultEditorKit.unselectAction*/, "shift LEFT", DefaultEditorKit.selectionBackwardAction, "shift RIGHT",
                        DefaultEditorKit.selectionForwardAction, "control LEFT", DefaultEditorKit.beginLineAction, "control RIGHT",
                        DefaultEditorKit.endLineAction, "control shift LEFT", DefaultEditorKit.selectionBeginLineAction,
                        "control shift RIGHT", DefaultEditorKit.selectionEndLineAction, "HOME", DefaultEditorKit.beginLineAction, "END",
                        DefaultEditorKit.endLineAction, "shift HOME", DefaultEditorKit.selectionBeginLineAction, "shift END",
                        DefaultEditorKit.selectionEndLineAction, "BACK_SPACE", DefaultEditorKit.deletePrevCharAction, "shift BACK_SPACE",
                        DefaultEditorKit.deletePrevCharAction, "ctrl H", DefaultEditorKit.deletePrevCharAction, "DELETE",
                        DefaultEditorKit.deleteNextCharAction, "RIGHT", DefaultEditorKit.forwardAction, "LEFT",
                        DefaultEditorKit.backwardAction, "KP_RIGHT", DefaultEditorKit.forwardAction, "KP_LEFT",
                        DefaultEditorKit.backwardAction, "ENTER", JTextField.notifyAction, "control shift O", "toggle-componentOrientation"
                        /*DefaultEditorKit.toggleComponentOrientation*/ } ) );

        // WebFormattedTextField actions
        table.put ( "FormattedTextField.focusInputMap", new UIDefaults.LazyInputMap (
                new Object[]{ "ctrl C", DefaultEditorKit.copyAction, "ctrl V", DefaultEditorKit.pasteAction, "ctrl X",
                        DefaultEditorKit.cutAction, "COPY", DefaultEditorKit.copyAction, "PASTE", DefaultEditorKit.pasteAction, "CUT",
                        DefaultEditorKit.cutAction, "control INSERT", DefaultEditorKit.copyAction, "shift INSERT",
                        DefaultEditorKit.pasteAction, "shift DELETE", DefaultEditorKit.cutAction, "shift LEFT",
                        DefaultEditorKit.selectionBackwardAction, "shift KP_LEFT", DefaultEditorKit.selectionBackwardAction, "shift RIGHT",
                        DefaultEditorKit.selectionForwardAction, "shift KP_RIGHT", DefaultEditorKit.selectionForwardAction, "ctrl LEFT",
                        DefaultEditorKit.previousWordAction, "ctrl KP_LEFT", DefaultEditorKit.previousWordAction, "ctrl RIGHT",
                        DefaultEditorKit.nextWordAction, "ctrl KP_RIGHT", DefaultEditorKit.nextWordAction, "ctrl shift LEFT",
                        DefaultEditorKit.selectionPreviousWordAction, "ctrl shift KP_LEFT", DefaultEditorKit.selectionPreviousWordAction,
                        "ctrl shift RIGHT", DefaultEditorKit.selectionNextWordAction, "ctrl shift KP_RIGHT",
                        DefaultEditorKit.selectionNextWordAction, "ctrl A", DefaultEditorKit.selectAllAction, "HOME",
                        DefaultEditorKit.beginLineAction, "END", DefaultEditorKit.endLineAction, "shift HOME",
                        DefaultEditorKit.selectionBeginLineAction, "shift END", DefaultEditorKit.selectionEndLineAction, "BACK_SPACE",
                        DefaultEditorKit.deletePrevCharAction, "shift BACK_SPACE", DefaultEditorKit.deletePrevCharAction, "ctrl H",
                        DefaultEditorKit.deletePrevCharAction, "DELETE", DefaultEditorKit.deleteNextCharAction, "ctrl DELETE",
                        DefaultEditorKit.deleteNextWordAction, "ctrl BACK_SPACE", DefaultEditorKit.deletePrevWordAction, "RIGHT",
                        DefaultEditorKit.forwardAction, "LEFT", DefaultEditorKit.backwardAction, "KP_RIGHT", DefaultEditorKit.forwardAction,
                        "KP_LEFT", DefaultEditorKit.backwardAction, "ENTER", JTextField.notifyAction, "ctrl BACK_SLASH", "unselect",
                        "control shift O", "toggle-componentOrientation", "ESCAPE", "reset-field-edit", "UP", "increment", "KP_UP",
                        "increment", "DOWN", "decrement", "KP_DOWN", "decrement", } ) );

        // Multiline areas actions
        Object multilineInputMap = new UIDefaults.LazyInputMap (
                new Object[]{ "control C", DefaultEditorKit.copyAction, "control V", DefaultEditorKit.pasteAction, "control X",
                        DefaultEditorKit.cutAction, "COPY", DefaultEditorKit.copyAction, "PASTE", DefaultEditorKit.pasteAction, "CUT",
                        DefaultEditorKit.cutAction, "control INSERT", DefaultEditorKit.copyAction, "shift INSERT",
                        DefaultEditorKit.pasteAction, "shift DELETE", DefaultEditorKit.cutAction, "shift LEFT",
                        DefaultEditorKit.selectionBackwardAction, "shift RIGHT", DefaultEditorKit.selectionForwardAction, "control LEFT",
                        DefaultEditorKit.previousWordAction, "control RIGHT", DefaultEditorKit.nextWordAction, "control shift LEFT",
                        DefaultEditorKit.selectionPreviousWordAction, "control shift RIGHT", DefaultEditorKit.selectionNextWordAction,
                        "control A", DefaultEditorKit.selectAllAction, "control BACK_SLASH", "unselect"
                        /*DefaultEditorKit.unselectAction*/, "HOME", DefaultEditorKit.beginLineAction, "END",
                        DefaultEditorKit.endLineAction, "shift HOME", DefaultEditorKit.selectionBeginLineAction, "shift END",
                        DefaultEditorKit.selectionEndLineAction, "control HOME", DefaultEditorKit.beginAction, "control END",
                        DefaultEditorKit.endAction, "control shift HOME", DefaultEditorKit.selectionBeginAction, "control shift END",
                        DefaultEditorKit.selectionEndAction, "UP", DefaultEditorKit.upAction, "DOWN", DefaultEditorKit.downAction,
                        "BACK_SPACE", DefaultEditorKit.deletePrevCharAction, "shift BACK_SPACE", DefaultEditorKit.deletePrevCharAction,
                        "ctrl H", DefaultEditorKit.deletePrevCharAction, "DELETE", DefaultEditorKit.deleteNextCharAction, "ctrl DELETE",
                        DefaultEditorKit.deleteNextWordAction, "ctrl BACK_SPACE", DefaultEditorKit.deletePrevWordAction, "RIGHT",
                        DefaultEditorKit.forwardAction, "LEFT", DefaultEditorKit.backwardAction, "KP_RIGHT", DefaultEditorKit.forwardAction,
                        "KP_LEFT", DefaultEditorKit.backwardAction, "PAGE_UP", DefaultEditorKit.pageUpAction, "PAGE_DOWN",
                        DefaultEditorKit.pageDownAction, "shift PAGE_UP", "selection-page-up", "shift PAGE_DOWN", "selection-page-down",
                        "ctrl shift PAGE_UP", "selection-page-left", "ctrl shift PAGE_DOWN", "selection-page-right", "shift UP",
                        DefaultEditorKit.selectionUpAction, "shift DOWN", DefaultEditorKit.selectionDownAction, "ENTER",
                        DefaultEditorKit.insertBreakAction, "TAB", DefaultEditorKit.insertTabAction, "control T", "next-link-action",
                        "control shift T", "previous-link-action", "control SPACE", "activate-link-action", "control shift O",
                        "toggle-componentOrientation"
                        /*DefaultEditorKit.toggleComponentOrientation*/ } );
        table.put ( "TextArea.focusInputMap", multilineInputMap );
        table.put ( "TextPane.focusInputMap", multilineInputMap );
        table.put ( "EditorPane.focusInputMap", multilineInputMap );

        // WebComboBox actions
        table.put ( "ComboBox.ancestorInputMap", new UIDefaults.LazyInputMap (
                new Object[]{ "ESCAPE", "hidePopup", "PAGE_UP", "pageUpPassThrough", "PAGE_DOWN", "pageDownPassThrough", "HOME",
                        "homePassThrough", "END", "endPassThrough", "DOWN", "selectNext", "KP_DOWN", "selectNext", "alt DOWN",
                        "togglePopup", "alt KP_DOWN", "togglePopup", "alt UP", "togglePopup", "alt KP_UP", "togglePopup", "SPACE",
                        "spacePopup", "ENTER", "enterPressed", "UP", "selectPrevious", "KP_UP", "selectPrevious" } ) );

        // WebFileChooser actions
        table.put ( "FileChooser.ancestorInputMap", new UIDefaults.LazyInputMap (
                new Object[]{ "ESCAPE", "cancelSelection", "F2", "editFileName", "F5", "refresh", "BACK_SPACE", "Go Up", "ENTER",
                        "approveSelection", "ctrl ENTER", "approveSelection" } ) );
    }

    /**
     * Initializes all default component fonts.
     *
     * @param table UIDefaults table
     */
    private static void initializeFonts ( UIDefaults table )
    {
        initializeFont ( table, "Button.font", buttonFont, globalTextFont );
        initializeFont ( table, "ToggleButton.font", toggleButtonFont, globalTextFont );
        initializeFont ( table, "RadioButton.font", radioButtonFont, globalTextFont );
        initializeFont ( table, "CheckBox.font", checkBoxFont, globalTextFont );
        initializeFont ( table, "ColorChooser.font", colorChooserFont, globalTextFont );
        initializeFont ( table, "ComboBox.font", comboBoxFont, globalEditorsFont );
        initializeFont ( table, "InternalFrame.titleFont", internalFrameFont, globalTitleFont );
        initializeFont ( table, "Label.font", labelFont, globalTextFont );
        initializeFont ( table, "List.font", listFont, globalTextFont );
        initializeFont ( table, "MenuBar.font", menuBarFont, globalTextFont );
        initializeFont ( table, "MenuItem.font", menuItemFont, globalTextFont );
        initializeFont ( table, "MenuItem.acceleratorFont", menuItemAcceleratorFont, globalAcceleratorFont );
        initializeFont ( table, "RadioButtonMenuItem.font", radioButtonMenuItemFont, globalTextFont );
        initializeFont ( table, "RadioButtonMenuItem.acceleratorFont", radioButtonMenuItemAcceleratorFont, globalAcceleratorFont );
        initializeFont ( table, "CheckBoxMenuItem.font", checkBoxMenuItemFont, globalTextFont );
        initializeFont ( table, "CheckBoxMenuItem.acceleratorFont", checkBoxMenuItemAcceleratorFont, globalAcceleratorFont );
        initializeFont ( table, "Menu.font", menuFont, globalTextFont );
        initializeFont ( table, "Menu.acceleratorFont", menuAcceleratorFont, globalAcceleratorFont );
        initializeFont ( table, "PopupMenu.font", popupMenuFont, globalTextFont );
        initializeFont ( table, "OptionPane.font", optionPaneFont, globalTextFont );
        initializeFont ( table, "Panel.font", panelFont, globalTextFont );
        initializeFont ( table, "ProgressBar.font", progressBarFont, globalTextFont );
        initializeFont ( table, "ScrollPane.font", scrollPaneFont, globalTextFont );
        initializeFont ( table, "Viewport.font", viewportFont, globalTextFont );
        initializeFont ( table, "Slider.font", sliderFont, globalTextFont );
        initializeFont ( table, "Spinner.font", spinnerFont, globalEditorsFont );
        initializeFont ( table, "TabbedPane.font", tabbedPaneFont, globalTextFont );
        initializeFont ( table, "Table.font", tableFont, globalTextFont );
        initializeFont ( table, "TableHeader.font", tableHeaderFont, globalTextFont );
        initializeFont ( table, "TextField.font", textFieldFont, globalEditorsFont );
        initializeFont ( table, "FormattedTextField.font", formattedTextFieldFont, globalEditorsFont );
        initializeFont ( table, "PasswordField.font", passwordFieldFont, globalEditorsFont );
        initializeFont ( table, "TextArea.font", textAreaFont, globalEditorsFont );
        initializeFont ( table, "TextPane.font", textPaneFont, globalEditorsFont );
        initializeFont ( table, "EditorPane.font", editorPaneFont, globalEditorsFont );
        initializeFont ( table, "TitledBorder.font", titledBorderFont, globalTextFont );
        initializeFont ( table, "ToolBar.font", toolBarFont, globalTextFont );
        initializeFont ( table, "ToolTip.font", toolTipFont, globalTextFont );
        initializeFont ( table, "Tree.font", treeFont, globalTextFont );
    }

    /**
     * Initializes single component font.
     *
     * @param table      UIDefaults table
     * @param key        component font key
     * @param font       custom font
     * @param globalFont global font
     */
    private static void initializeFont ( UIDefaults table, String key, Font font, Font globalFont )
    {
        table.put ( key, createLazyFont ( font != null ? font : globalFont ) );
    }

    /**
     * Returns SwingLazyValue for specified font.
     *
     * @param font font
     * @return SwingLazyValue for specified font
     */
    private static SwingLazyValue createLazyFont ( Font font )
    {
        return new SwingLazyValue ( "javax.swing.plaf.FontUIResource", null,
                new Object[]{ font.getName (), font.getStyle (), font.getSize () } );
    }

    /**
     * Initializes custom WebLookAndFeel features.
     */
    public void initialize ()
    {
        super.initialize ();

        // Installs a tricky thread exception handler that will output any uncaught error that occurs in Event Dispatch Thread
        SwingUtils.invokeAndWaitSafely ( new Runnable ()
        {
            public void run ()
            {
                Thread.setDefaultUncaughtExceptionHandler ( new Thread.UncaughtExceptionHandler ()
                {
                    public void uncaughtException ( Thread t, Throwable e )
                    {
                        e.printStackTrace ();
                    }
                } );
            }
        } );

        // Listening to ALT key for menubar quick focusing
        KeyboardFocusManager.getCurrentKeyboardFocusManager ().addKeyEventPostProcessor ( altProcessor );

        // Initialize managers only when L&F was changed
        UIManager.addPropertyChangeListener ( new PropertyChangeListener ()
        {
            public void propertyChange ( PropertyChangeEvent evt )
            {
                if ( evt.getPropertyName ().equals ( "lookAndFeel" ) )
                {
                    // Web decoration for frames and dialogs
                    JFrame.setDefaultLookAndFeelDecorated ( decorateFrames );
                    JDialog.setDefaultLookAndFeelDecorated ( decorateDialogs );

                    // Initializing managers if Web L&F was installed
                    if ( evt.getNewValue () instanceof WebLookAndFeel )
                    {
                        initializeManagers ();
                    }

                    // Remove listener in any case a
                    UIManager.removePropertyChangeListener ( this );
                }
            }
        } );
    }

    /**
     * Uninitializes custom WebLookAndFeel features.
     */
    public void uninitialize ()
    {
        super.uninitialize ();

        // Removing alt processor
        KeyboardFocusManager.getCurrentKeyboardFocusManager ().removeKeyEventPostProcessor ( altProcessor );
    }

    /**
     * Hides or displays button mnemonics.
     *
     * @param hide whether hide button mnemonics or not
     */
    public static void setMnemonicHidden ( boolean hide )
    {
        isMnemonicHidden = !UIManager.getBoolean ( "Button.showMnemonics" ) && hide;
    }

    /**
     * Returns whether button mnemonics are hidden or not.
     *
     * @return true if button mnemonics are hidden, false otherwise
     */
    public static boolean isMnemonicHidden ()
    {
        if ( UIManager.getBoolean ( "Button.showMnemonics" ) )
        {
            isMnemonicHidden = false;
        }
        return isMnemonicHidden;
    }

    /**
     * Installs WebLookAndFeel in one simple call.
     *
     * @return true if WebLookAndFeel was successfuly installed, false otherwise
     */
    public static boolean install ()
    {
        return install ( false );
    }

    /**
     * Installs WebLookAndFeel in one simple call and updates all existing components if requested.
     *
     * @param updateExistingComponents whether update all existing components or not
     * @return true if WebLookAndFeel was successfuly installed, false otherwise
     */
    public static boolean install ( boolean updateExistingComponents )
    {
        try
        {
            // Installing LookAndFeel
            UIManager.setLookAndFeel ( new WebLookAndFeel () );

            // Updating already created components tree
            if ( updateExistingComponents )
            {
                updateAllComponentUIs ();
            }

            // LookAndFeel installed sucessfully
            return true;
        }
        catch ( Throwable e )
        {
            // Printing exception
            e.printStackTrace ();

            // LookAndFeel installation failed
            return false;
        }
    }

    /**
     * Returns whether WebLookAndFeel is installed or not.
     *
     * @return true if WebLookAndFeel is installed, false otherwise
     */
    public static boolean isInstalled ()
    {
        return UIManager.getLookAndFeel ().getClass ().getCanonicalName ().equals ( WebLookAndFeel.class.getCanonicalName () );
    }

    /**
     * Initializes library managers.
     * Initialization order is strict since some managers require other managers to be loaded.
     */
    public static void initializeManagers ()
    {
        VersionManager.initialize ();
        LanguageManager.initialize ();
        SettingsManager.initialize ();
        HotkeyManager.initialize ();
        FocusManager.initialize ();
        TooltipManager.initialize ();
        ProxyManager.initialize ();
    }

    /**
     * Returns a list of square WebLookAndFeel images that can be used as window icons on any OS.
     *
     * @return list of square WebLookAndFeel images
     */
    public static List<Image> getImages ()
    {
        loadIcons ();
        return ImageUtils.toImagesList ( icons );
    }

    /**
     * Returns a list of square WebLookAndFeel icons that can be used as window icons on any OS.
     *
     * @return list of square WebLookAndFeel icons
     */
    public static List<ImageIcon> getIcons ()
    {
        loadIcons ();
        return CollectionUtils.copy ( icons );
    }

    /**
     * Returns square WebLookAndFeel image of specified size.
     *
     * @param size square WebLookAndFeel image size
     * @return square WebLookAndFeel image
     */
    public static Image getImage ( int size )
    {
        return getIcon ( size ).getImage ();
    }

    /**
     * Returns square WebLookAndFeel icon of specified size.
     *
     * @param size square WebLookAndFeel icon size
     * @return square WebLookAndFeel icon
     */
    public static ImageIcon getIcon ( int size )
    {
        loadIcons ();
        for ( ImageIcon icon : icons )
        {
            if ( icon.getIconWidth () == size )
            {
                return icon;
            }
        }
        return null;
    }

    /**
     * Loads square WebLookAndFeel icons listed in icons.xml file in resources folder.
     * Loaded icons will have the same order they have in that xml file.
     */
    private static void loadIcons ()
    {
        if ( icons == null )
        {
            icons = XmlUtils.loadImagesList ( WebLookAndFeel.class.getResource ( "resources/icons.xml" ) );
        }
    }

    /**
     * Returns a beter disabled icon than BasicLookAndFeel offers.
     * Generated icons won't be cached here, so make sure you don't call this method directly from some paint methods.
     *
     * @param component component that requests disabled icon
     * @param icon      normal icon
     * @return disabled icon
     */
    public Icon getDisabledIcon ( JComponent component, Icon icon )
    {
        if ( icon instanceof ImageIcon )
        {
            return ImageUtils.createDisabledCopy ( ( ImageIcon ) icon );
        }
        return null;
    }

    /**
     * Forces global components UI update in all existing application windows.
     */
    public static void updateAllComponentUIs ()
    {
        for ( Window window : Window.getWindows () )
        {
            SwingUtilities.updateComponentTreeUI ( window );
        }
    }

    /**
     * Returns custom WebLookAndFeel layout style.
     *
     * @return custom WebLookAndFeel layout style
     */
    public LayoutStyle getLayoutStyle ()
    {
        return WebLayoutStyle.INSTANCE;
    }

    /**
     * Returns whether look and feel uses custom decoration for newly created frames or not.
     *
     * @return true if look and feel uses custom decoration for newly created frames, false otherwise
     */
    public static boolean isDecorateFrames ()
    {
        return decorateFrames;
    }

    /**
     * Sets whether look and feel should use custom decoration for newly created frames or not.
     *
     * @param decorateFrames whether look and feel should use custom decoration for newly created frames or not
     */
    public static void setDecorateFrames ( boolean decorateFrames )
    {
        WebLookAndFeel.decorateFrames = decorateFrames;
        JFrame.setDefaultLookAndFeelDecorated ( decorateFrames );
    }

    /**
     * Returns whether look and feel uses custom decoration for newly created dialogs or not.
     *
     * @return true if look and feel uses custom decoration for newly created dialogs, false otherwise
     */
    public static boolean isDecorateDialogs ()
    {
        return decorateDialogs;
    }

    /**
     * Sets whether look and feel should use custom decoration for newly created dialogs or not.
     *
     * @param decorateDialogs whether look and feel should use custom decoration for newly created dialogs or not
     */
    public static void setDecorateDialogs ( boolean decorateDialogs )
    {
        WebLookAndFeel.decorateDialogs = decorateDialogs;
        JDialog.setDefaultLookAndFeelDecorated ( decorateDialogs );
    }

    /**
     * Sets whether look and feel should use custom decoration for newly created frames and dialogs or not.
     *
     * @param decorateAllWindows whether look and feel should use custom decoration for newly created frames and dialogs or not
     */
    public static void setDecorateAllWindows ( boolean decorateAllWindows )
    {
        setDecorateFrames ( decorateAllWindows );
        setDecorateDialogs ( decorateAllWindows );
    }

    /**
     * Returns whether LTR is current global component orientation or not.
     *
     * @return true if LTR is current global component orientation, false otherwise
     */
    public static boolean isLeftToRight ()
    {
        return LanguageManager.isLeftToRight ();
    }

    /**
     * Returns current global component orientation.
     *
     * @return current global component orientation
     */
    public static ComponentOrientation getOrientation ()
    {
        return LanguageManager.getOrientation ();
    }

    /**
     * Sets current global component orientation.
     *
     * @param orientation new global component orientation
     */
    public static void setOrientation ( ComponentOrientation orientation )
    {
        LanguageManager.setOrientation ( orientation );
    }

    /**
     * Changes current global component orientation to opposite one.
     */
    public static void changeOrientation ()
    {
        LanguageManager.changeOrientation ();
    }
}